#include "CommandServer_p.h"

#include <cassert>

#include <QtNetwork/QTcpSocket>
#include <QtCore/QRegexp>
#include <QtCore/QStringList>

#include "ProtocolError.h"

std::unique_ptr< CommandServer, std::function< void ( CommandServer * ) > > CommandServer::Private::self( nullptr, &Private::destructor );

void CommandServer::Private::destructor( CommandServer * pointer ) {
	delete pointer;
}

CommandServer::Private::Private( CommandServer * owner ):
QObject(),
owner( owner ),
server( new QTcpServer( this ) ),
socket( nullptr ),
commands(),
async( true ) {
	this->connect( this->server, SIGNAL( newConnection() ), SLOT( onNewConnection() ) );
}

void CommandServer::Private::onNewConnection() {
	while( this->server->hasPendingConnections() ) {
		QTcpSocket * socket = this->server->nextPendingConnection();
		if( this->socket != nullptr ) {
			// we only accept one session
			socket->disconnectFromHost();
			socket->deleteLater();
		}
		this->socket = socket;
		this->connect( socket, SIGNAL( readyRead() ), SLOT( onSocketReadyRead() ) );
		this->connect( socket, SIGNAL( disconnected() ), SLOT( onSocketDisconnected() ) );
		this->connect( socket, SIGNAL( error( QAbstractSocket::SocketError ) ), SLOT( onSocketError( QAbstractSocket::SocketError ) ) );
	}
}

/*
NOTE: this method will pass through event loop, so it shouldn't throw exceptions
*/
void CommandServer::Private::onSocketReadyRead() {
	if( !this->async ) {
		return;
	}
	QString data = QString::fromUtf8( this->socket->readAll().trimmed() );
	QStringList chunks = data.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
	if( chunks.back() != "<ENDLINE>" ) {
		// TODO: emit error signal
		assert( !"protocol error" );
	}
	data = chunks.front();
	chunks = chunks.mid( 1, chunks.size() - 2 );

	auto it = this->commands.find( data );
	if( it == this->commands.end() ) {
		// TODO: emit error signal
		assert( !"protocol error" );
	}
	it->second( chunks );
}

void CommandServer::Private::onSocketError( QAbstractSocket::SocketError socketError ) {
}

void CommandServer::Private::onSocketDisconnected() {
	this->socket->deleteLater();
	this->socket = nullptr;
}

void CommandServer::initialize() {
	if( !Private::self ) {
		Private::self.reset( new CommandServer );
	}
}

CommandServer & CommandServer::instance() {
	assert( Private::self || !"CommandServer not initialized yet" );
	return *Private::self;
}

CommandServer::CommandServer():
QObject(),
p_( new Private( this ) ) {
	this->p_->commands.insert( std::make_pair( "visualize", [this]( const QStringList & args )->void {
		emit this->visualize( args.front() );
	} ) );
	this->p_->commands.insert( std::make_pair( "update", [this]( const QStringList & )->void {
		emit this->update();
	} ) );
	this->p_->commands.insert( std::make_pair( "anime", [this]( const QStringList & args )->void {
		emit this->anime( args.front() );
	} ) );
}

CommandServer::~CommandServer() {
	if( this->p_->socket ) {
		// cleanup socket
		this->p_->socket->disconnectFromHost();
	}
}

bool CommandServer::listen( const QHostAddress & address, quint16 port ) {
	return this->p_->server->listen( address, port );
}

QVariant CommandServer::ask( const QString & variableName ) {
	// format: <start>\nask varname\n<end>\n
	this->p_->async = false;
	this->p_->socket->write( QString( "<start>\nask %1\n<end>\n" ).arg( variableName ).toUtf8() );
	if( !this->p_->socket->waitForReadyRead( -1 ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	this->p_->async = true;
	QString data = QString::fromUtf8( this->p_->socket->readAll().trimmed() );
	QRegExp pattern( "<VARNAME>\\s+(.+)\\s+<VARTYPE>\\s+(.+)\\s+<ADDR>\\s+(.+)\\s+<ENDLINE>" );
	if( !pattern.exactMatch( data ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	QVariantMap m;
	m.insert( "<VARNAME>", pattern.cap( 1 ) );
	m.insert( "<VARTYPE>", pattern.cap( 2 ) );
	m.insert( "<ADDR>", pattern.cap( 3 ) );
	return m;
}

QVariant CommandServer::line() {
	// format: <start>\nline\n<end>\n
	this->p_->async = false;
	this->p_->socket->write( QString( "<start>\nline\n<end>\n" ).toUtf8() );
	if( !this->p_->socket->waitForReadyRead( -1 ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	this->p_->async = true;
	QString data = QString::fromUtf8( this->p_->socket->readAll().trimmed() );
	QRegExp pattern( "(\\d+)\\s+<ENDLINE>" );
	if( !pattern.exactMatch( data ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	return pattern.cap( 1 ).toInt();
}

QVariant CommandServer::show( const QString & variableName ) {
	// format: <start>\nshow varname\n<end>\n
	this->p_->async = false;
	this->p_->socket->write( QString( "<start>\nshow %1\n<end>\n" ).arg( variableName ).toUtf8() );
	if( !this->p_->socket->waitForReadyRead( -1 ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	this->p_->async = true;
	QString data = QString::fromUtf8( this->p_->socket->readAll().trimmed() );
	QRegExp pattern( "<VARNAME>\\s+(.+)\\s+<VALUE>\\s+(.+)\\s+<ENDLINE>" );
	if( !pattern.exactMatch( data ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	QVariantMap m;
	m.insert( "<VARNAME>", pattern.cap( 1 ) );
	m.insert( "<VALUE>", pattern.cap( 2 ) );
	return m;
}

QVariant CommandServer::structure( const QString & className ) {
	// format: <start>\nstruct varname\n<end>\n
	this->p_->async = false;
	this->p_->socket->write( QString( "<start>\nstruct %1\n<end>\n" ).arg( className ).toUtf8() );
	if( !this->p_->socket->waitForReadyRead( -1 ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	this->p_->async = true;
	QString data = QString::fromUtf8( this->p_->socket->readAll().trimmed() );
	QRegExp pattern( "^<CLASSNAME>\\s+(.+)\\s+<ATTQUN>\\s+(\\d+)(.*)<ENDLINE>$" );
	if( !pattern.exactMatch( data ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	QVariantMap m;
	m.insert( "<CLASSNAME>", pattern.cap( 1 ) );
	int count = pattern.cap( 2 ).toInt();

	data = pattern.cap( 3 ).trimmed();
	pattern.setPattern( "<VARNAME>\\s+(\\S+)\\s+<VARTYPE>\\s+(\\S+)" );
	QVariantList l;
	for( int pos = 0; ( pos = pattern.indexIn( data, pos ) ) != -1; pos += pattern.matchedLength() ) {
		QVariantMap mm;
		mm.insert( "<VARNAME>", pattern.cap( 1 ) );
		mm.insert( "<VARTYPE>", pattern.cap( 2 ) );
		l.push_back( mm );
	}
	assert( l.size() == count );
	m.insert( "<ATTRS>", l );
	return m;
}

QVariant CommandServer::continue_() {
	this->p_->socket->write( QString( "<CONT>\n" ).toUtf8() );
	return QVariant();
}

QVariant CommandServer::step() {
	this->p_->socket->write( QString( "<STEP>\n" ).toUtf8() );
	return QVariant();
}

QVariant CommandServer::next() {
	this->p_->socket->write( QString( "<NEXT>\n" ).toUtf8() );
	return QVariant();
}

QVariant CommandServer::over() {
	this->p_->socket->write( QString( "<OVER>\n" ).toUtf8() );
	return QVariant();
}

void CommandServer::start() {
	this->p_->socket->write( QString( "<start>\n" ).toUtf8() );
}

void CommandServer::end() {
	this->p_->socket->write( QString( "<end>\n" ).toUtf8() );
}

void CommandServer::syncStart() {
	this->p_->socket->write( QString( "<sync_start>\n" ).toUtf8() );
}

void CommandServer::syncEnd() {
	this->p_->socket->write( QString( "<sync_end>\n" ).toUtf8() );
}

QVariant CommandServer::scope( const QString & variableName ) {
	// format: <start>\nscope varname\n<end>\n
	this->p_->async = false;
	this->p_->socket->write( QString( "<start>\nscope %1\n<end>\n" ).arg( variableName ).toUtf8() );
	if( !this->p_->socket->waitForReadyRead( -1 ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	this->p_->async = true;
	QString data = QString::fromUtf8( this->p_->socket->readAll().trimmed() );
	QRegExp pattern( "<VARNAME>\\s+.+\\s+<VALUE>\\s+(true|false)\\s+<ENDLINE>" );
	if( !pattern.exactMatch( data ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	return pattern.cap( 1 ) == "true";
}

QVariant CommandServer::dangling( const QString & referenceName ) {
	// format: <start>\ndangling refname\n<end>\n
	this->p_->async = false;
	this->p_->socket->write( QString( "<start>\ndangling %1\n<end>\n" ).arg( referenceName ).toUtf8() );
	if( !this->p_->socket->waitForReadyRead( -1 ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	this->p_->async = true;
	QString data = QString::fromUtf8( this->p_->socket->readAll().trimmed() );
	QRegExp pattern( "(\\d+)\\s+<ENDLINE>" );
	if( !pattern.exactMatch( data ) ) {
		throw ProtocolError( "protocol error", __FILE__, __LINE__ );
	}
	return pattern.cap( 1 ).toInt();
}

void CommandServer::animeEnd(){
	this->p_->socket->write( QString( "<anime_end>\n" ).toUtf8() );
}

